// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.IO;
using System.Xml.XPath;

using Microsoft.TeamFoundation.Migration.Toolkit.Wit;

namespace Microsoft.TeamFoundation.Migration.Toolkit
{
    public class MigrationConfiguration : SettingsSection, IDisposable
    {
        public static MigrationConfiguration Current
        {
            get
            {
                return s_current;
            }
        }

        public static Dictionary<string, MigrationProvider> Providers
        {
            get
            {
                return s_providers;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Tfs")]
        public static Dictionary<string, TfsServer> TfsServers
        {
            get
            {
                return s_tfsServers;
            }
        }

        public VersionControlConfiguration VC
        {
            get
            {
                return m_vc;
            }
        }

        public WorkItemTrackingConfiguration Wit
        {
            get
            {
                return m_wit;
            }
        }

        public static SqlConnectionDescription Sql { get { return s_sql; } }

        public static MigrationConfiguration Load(Stream configStream)
        {
            try
            {
                initialize();

                // Validate input against the schema
                XmlSchema schema = LoadSchema();
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.Schemas.Add(schema);
                settings.ValidationType = ValidationType.Schema;
                settings.IgnoreComments = true;
                settings.IgnoreProcessingInstructions = true;
                settings.IgnoreWhitespace = true;

                using (XmlReader reader = XmlReader.Create(configStream, settings))
                {
                    XPathDocument xpath = new XPathDocument(reader);
                    XPathNavigator nav = xpath.CreateNavigator();

                    XPathNodeIterator providers = nav.Select("//Migration/Providers/Provider");

                    foreach (XPathNavigator provider in providers)
                    {
                        MigrationProvider prov = LoadProvider(provider.OuterXml);
                        MigrationConfiguration.Providers.Add(prov.Id, prov);
                    }

                    // Load the global settings
                    XPathNodeIterator globalSettings = nav.Select("//Migration/Settings/Setting");
                    Current.InitFromXml(globalSettings);

                    // Load the TFS Servers
                    XPathNodeIterator tfsServers = nav.Select("//Migration/Servers/Tfs");

                    foreach (XPathNavigator tfsServerNav in tfsServers)
                    {
                        TfsServer server = LoadTfsServer(tfsServerNav);
                        MigrationConfiguration.TfsServers.Add(server.Id, server);
                    }

                    // Load SQL data
                    XPathNavigator sqlNav = nav.SelectSingleNode("/Migration/Sql");
                    s_sql = SqlConnectionDescription.Load(sqlNav.OuterXml);
                    
                    XPathNodeIterator migrationRoot = nav.Select("//Migration/*");
                    if (migrationRoot != null)
                    {
                        foreach (XPathNavigator navRoot in migrationRoot)
                        {
                            switch (navRoot.Name)
                            {
                                case "Servers":
                                case "Providers":
                                case "Settings":
                                case "Sql":
                                    // ignore - these were handled earlier
                                    continue;
                                default:
                                    if (MigrationConfiguration.Providers.ContainsKey(navRoot.Name))
                                    {
                                        MigrationProvider rootProv = MigrationConfiguration.Providers[navRoot.Name];
                                        IConfigFactory factory = LoadProviderFactory(rootProv);
                                        IMigrationProvider migrationHost = (IMigrationProvider)factory.CreateInstance(typeof(IMigrationProvider), navRoot.OuterXml);
                                        s_migrationTargets[navRoot.Name] = migrationHost;
                                    }
                                    else
                                    {
                                        throw new InitializationException(string.Format(MigrationToolkitResources.Culture, MigrationToolkitResources.UnknownMigrationTarget, navRoot.Name));
                                    }
                                    break;

                            }
                        }

                        if (s_migrationTargets.ContainsKey("VC"))
                        {
                            Current.m_vc = (VersionControlConfiguration)s_migrationTargets["VC"];
                        }
                        if (s_migrationTargets.ContainsKey("WIT"))
                        {
                            Current.m_wit = (WorkItemTrackingConfiguration)s_migrationTargets["WIT"];
                        }

                        // do at the end so that initialization is effectively complete
                        foreach (IMigrationProvider prov in s_migrationTargets.Values)
                        {
                            prov.RegisterParent(Current);
                        }
                    }

                    return Current;
                }
            }
            catch (InitializationException)
            {
                throw;
            }
            catch (XmlSchemaValidationException)
            {
                throw;
            }
            catch (Exception e)
            {
                throw new InitializationException(
                    string.Format(MigrationToolkitResources.Culture,
                    MigrationToolkitResources.UnknownInitError,
                    e.Message),
                    e);
            }
        }

        private static void initialize()
        {
            if (s_current != null)
            {
                s_current.Dispose();
                s_current = null;
            }

            s_current = new MigrationConfiguration();

            s_providers = new Dictionary<string, MigrationProvider>();

            s_migrationTargets = new Dictionary<string, IMigrationProvider>();

            // Register top-level providers
            RegisterProvider("VC", typeof(VersionControlConfigurationFactory));
            RegisterProvider("WIT", typeof(WorkItemTrackingConfigurationFactory));

            s_tfsServers = new Dictionary<string, TfsServer>();
        }

        /// <summary>
        /// Registers top-level provider.
        /// </summary>
        /// <param name="id">Provider id</param>
        /// <param name="type">Factory type</param>
        private static void RegisterProvider(
            string id,
            Type type)
        {
            MigrationProvider provider = new MigrationProvider();
            provider.AssemblyQualifiedName = type.AssemblyQualifiedName;
            provider.Id = id;
            Debug.Assert(!MigrationConfiguration.Providers.ContainsKey(id), "Duplicate provider id!");
            MigrationConfiguration.Providers.Add(id, provider);
        }

        private static TfsServer LoadTfsServer(XPathNavigator tfsServerNav)
        {
            return (TfsServer)MigrationUtil.LoadTypedData(tfsServerNav.OuterXml, typeof(TfsServer));
        }

        private static MigrationProvider LoadProvider(string xmlString)
        {
            using (StringReader sr = new StringReader(xmlString))
            {
                XmlSerializer xs = new XmlSerializer(typeof(MigrationProvider));
                MigrationProvider prov = (MigrationProvider)xs.Deserialize(sr);
                return prov;
            }
        }

        private static IConfigFactory LoadProviderFactory(MigrationProvider provider)
        {
            return (IConfigFactory)Activator.CreateInstance(provider.ProviderType);
        }

        private static XmlSchema LoadSchema()
        {
            using (Stream stream = typeof(MigrationConfiguration).Assembly.GetManifestResourceStream("Microsoft.TeamFoundation.Migration.Toolkit.Configuration.Schema.xsd"))
            {
                return XmlSchema.Read(stream, null);
            }
        }

        #region IDisposable Members

        protected virtual void Dispose(bool disposing)
        {
            if (!m_disposed)
            {
                if (disposing)
                {
                    if (s_migrationTargets != null)
                    {
                        foreach (IMigrationProvider existingTarget in s_migrationTargets.Values)
                        {
                            existingTarget.Dispose();
                        }
                    }
                }

                // release unmanged resources
            }

            m_disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        private static MigrationConfiguration s_current;
        private static Dictionary<string, MigrationProvider> s_providers;
        private static Dictionary<string, TfsServer> s_tfsServers;
        private static Dictionary<string, IMigrationProvider> s_migrationTargets;
        private static SqlConnectionDescription s_sql;

        private VersionControlConfiguration m_vc;
        private WorkItemTrackingConfiguration m_wit;

        bool m_disposed;
    }
}
